/* Copyright (c) 2004-2008, Andriy Nikolov
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.processmining.email;

import java.io.*;
import java.util.*;
import javax.xml.parsers.*;

import javax.swing.*;

import org.xml.sax.*;

/**
 * <p>Represents an instance of the process log mined from the e-mail log.
 *
 * @author Andriy Nikolov
 * @version 1.0
 */
public class ProcessLog {

  Hashtable caseList;
  Vector orderedCaseList;
  Vector taskTypesList;
  Vector eventTypes;
  Hashtable eventTypesTable;


  /**
   * Array that contains standard event types of the ProM framework:
   * "schedule", "start", "complete", "suspend", "resume", "withdraw" and
   * "abort".
   */
  public static final String standardEventTypes[] = {
    "schedule",
    "start",
    "complete",
    "suspend",
    "resume",
    "withdraw",
    "abort"
  };

  EMailLog eMailLog;

  int caseSource;
  int taskSource;
  int eventSource;

  boolean allowPartialCoincidenceCases = false;
  boolean allowPartialCoincidenceTasks = false;
  boolean assignMissingEvents = false;
  boolean saveMessageData = false;
  boolean ignoreReplies = false;

  public static final int CASE_LINKED_CONTACT = 1;
  public static final int CASE_SENDER = 2;
  public static final int CASE_RECIPIENT = 3;
  public static final int CASE_SINGLE_CASE = 4;
  public static final int CASE_SUBJECT = 5;
  public static final int CASE_ANYWHERE = 6;
  public static final int CASE_THREAD = 7;

  public static final int TASK_DEFAULT = 1;
  public static final int TASK_SUBJECT = 2;

  public static final int EVENT_DEFAULT = 1;
  public static final int EVENT_SUBJECT = 2;
  public static final int EVENT_ONETYPE = 3;

  public ProcessLog() {
    try {
      jbInit();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Creates an instance of process log and sets the reference to the specific
   * EMailLog. Note: no mining is performed.
   *
   * @param log EMailLog
   */
  public ProcessLog(EMailLog log) {
    eMailLog = log;
    eMailLog.setProcessLog(this);
    try {
      jbInit();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void jbInit() {
    int i;
    ProcessEventType tmp;
    caseList = new Hashtable();
    orderedCaseList = new Vector();
    taskTypesList = new Vector();
    eventTypes = new Vector();
    eventTypesTable = new Hashtable();
    caseSource = 1;
    taskSource = 1;
    eventSource = 1;
    // Fill standard event types
    for (i=0;i<standardEventTypes.length;i++) {
      tmp = new ProcessEventType(standardEventTypes[i]);
      eventTypes.add(tmp);
      eventTypesTable.put(standardEventTypes[i], tmp);
    }
  }

  public EMailLog getEMailLog() {
    return eMailLog;
  }

  public void setEMailLog(EMailLog log){
    eMailLog  = log;
  }

  public void setIgnoreReplies(boolean ignoreReplies) {
    this.ignoreReplies = ignoreReplies;
  }

  /**
   * Gets the list of event types, which can appear in the process log.
   *
   * @return Vector
   */
  public Vector getEventTypesList() {
    return eventTypes;
  }

  /**
   * Gets a hashtable of allowed event types.
   *
   * @return Hashtable
   */
  public Hashtable getEventTypesTable() {
    return eventTypesTable;
  }

  private boolean canAddEventType(String standardName, String aliasName) {
    ProcessEventType tmp;
    for(int i=0;i<eventTypes.size();i++) {
      tmp = (ProcessEventType)eventTypes.get(i);
      if(tmp.getStandardName().equalsIgnoreCase(standardName)) {
        if(!tmp.getStandardName().equals("unknown")) {
          return false;
        } else {
          if (tmp.getAliasName().equalsIgnoreCase(aliasName)) {
            return false;
          }
        }
      }
    }
    return true;
  }

  /**
   * Adds new event type into the list if it is possible (i.e. an event with the
   * same name or alias does not exist already).
   *
   * @param standardName The name according to the events finite state machine
   *   model (one of the standard names or "unknown").
   * @param aliasName The name, under which the event is mentioned in the
   *   e-mail log. E.g. "finish" can be an alias for "complete"
   */
  public ProcessEventType addEventType(String standardName, String aliasName) {
    ProcessEventType tmp;
    if(canAddEventType(standardName, aliasName)) {
      tmp = new ProcessEventType(standardName, aliasName);
      eventTypes.add(tmp);
      eventTypesTable.put(aliasName, tmp);
      return tmp;
    } else {
      return null;
    }
  }

  /**
   * Removes from the list an event type by its index.
   *
   * @param index int
   */
  public void removeEventType(int index) {
    ProcessEventType tmp;
    if(index>=standardEventTypes.length) {
      tmp = (ProcessEventType)eventTypes.get(index);
      eventTypesTable.remove(tmp.getAliasName());
      eventTypes.remove(index);
    }
  }

  /**
   * Gets the list of process cases in the process log.
   *
   * @return Vector
   */
  public Vector getOrderedCaseList() {
    return orderedCaseList;
  }

  /**
   * Gets the list of possible task types in the process log.
   *
   * @return Vector
   */
  public Vector getTaskTypesList() {
    return taskTypesList;
  }

  /**
   * Adds new task type to the list.
   *
   * @param name String
   */
  public ProcessTaskType addTaskType(String name) {
    int i, j;
    boolean exists;
    ProcessTaskType curTaskType;
    String tmpName;

    exists = false;
    for(i=0;i<taskTypesList.size();i++) {
      curTaskType = (ProcessTaskType)taskTypesList.get(i);
      for(j=0;j<curTaskType.getAliases().size();j++) {
        if (curTaskType.getAlias(j).toLowerCase().equals(name.toLowerCase())) {
          return curTaskType;
        }
      }
    }
    curTaskType = new ProcessTaskType(name);
    taskTypesList.add(curTaskType);
    return curTaskType;
  }

  /**
   * Gets a task type with the specific index.
   *
   * @param index int
   * @return ProcessTaskType
   */
  public ProcessTaskType getTaskType(int index) {
    return (ProcessTaskType)taskTypesList.get(index);
  }

  /**
   * Gets the task type by its name.
   *
   * @param name String
   * @return ProcessTaskType
   */
  public ProcessTaskType getTaskTypeByName(String name) {
    ProcessTaskType curType, resType;
    resType = null;

    for(int i=0;i<taskTypesList.size();i++) {
      curType = (ProcessTaskType)taskTypesList.get(i);
      for(int j=0;j<curType.getAliases().size();j++) {
        if (curType.getAlias(j).toLowerCase().equals(name.toLowerCase())) {
          resType = curType;
        }
      }
    }
    return resType;
  }

  /**
   * Removes a task type with the specific index.
   *
   * @param index int
   */
  public void removeTaskType(int index) {
    try {
      taskTypesList.remove(index);
    } catch (IndexOutOfBoundsException e) {

    }
  }

  /**
   * Gets the hashtable of cases. The keys are the names of the cases.
   *
   * @return Hashtable
   */
  public Hashtable getCaseList() {
    return caseList;
  }

  public boolean isIgnoreReplies() {
    return ignoreReplies;
  }

  /**
   * Returns a task-user table, which provides the data for the User-Task
   * diagram.
   *
   * @return TaskPersonTable
   */
  public TaskPersonTable getTaskPersonTable() {
    TaskPersonTable table = new TaskPersonTable(this);
    return table;
  }

  /**
   * Returns a case-user table, which provides the data for the User-Case
   * diagram.
   *
   * @return CasePersonTable
   */
  public CasePersonTable getCasePersonTable() {
    CasePersonTable table = new CasePersonTable(this);
    table.calculateTable();
    return table;
  }

  /**
   * Writes a report about the process log into a JTextArea.
   *
   * @param textArea JTextArea
   */
  public void write(JTextArea textArea) {
    ProcessCase iterCase;
    Enumeration enumCases;
    int i;
    textArea.append("Building process log...\n");
    i = 1;
    enumCases = caseList.elements();
    while(enumCases.hasMoreElements()) {
      iterCase = (ProcessCase)enumCases.nextElement();
      textArea.append("Case "+i+"\n");
      iterCase.write(textArea);
      i++;
    }
    textArea.append("End of the log \n");
  }

  /**
   * Specifies where the case description will be searched in the message. Can
   * be one of:
   * 	CASE_LINKED_CONTACT - the case name is a person linked to the message;
   * 	CASE_SENDER - the case name is a person who sent the message;
   *   	CASE_RECIPIENT - the case name is one of the recipients;
   *   	CASE_SINGLE_CASE - all e-mail log represents one single	process case;
   * 	CASE_SUBJECT - the case name is contained in the message subject;
   *   	CASE_THREAD - each thread of messages represents a case;
   * CASE_ANYWHERE - the case name can appear in the subject line or among the
   * sender/recipients list.
   *
   * @param type int
   */
  public void setCaseSourceType(int type) {
    caseSource = type;
  }

  public int getCaseSourceType() {
    return caseSource;
  }

  /**
   * Specifies how the event type will be determined. Can accept: EVENT_ONETYPE
   * - all events are of one type ("unknown"-"normal"); EVENT_SUBJECT - the
   * event name is contained in the subject; EVENT_DEFAULT - the message is
   * tagged with the event type according to the standards.
   *
   * @param type int
   */
  public void setEventSourceType(int type) {
    eventSource = type;
    ProcessEventType newType;
    if(type==EVENT_ONETYPE) {
      for(int i=0;i<eventTypes.size();i++) {
        newType = (ProcessEventType)eventTypes.get(i);
        if(newType.getAliasName().equals("normal")) {
          return;
        }
      }
      addEventType("unknown", "normal");
    }
  }

  public int getEventSourceType() {
    return eventSource;
  }

  /**
   * Specifies how the task type will be determined. Can accept: 	 TASK_SUBJECT -
   * the task name is contained in the subject; TASK_DEFAULT - the message is
   * tagged with the task name according to the standards.
   *
   * @param type int
   */
  public void setTaskSourceType(int type) {
    taskSource = type;
  }

  public int getTaskSourceType() {
    return taskSource;
  }

  /**
   * Checks if the algorithm should try to insert missing events when mining the
   * log.
   *
   * @return boolean
   */
  public boolean hasToAssignMissingEvents() {
    return assignMissingEvents;
  }

  public void setHasToAssignMissingEvents(boolean value) {
    assignMissingEvents = value;
  }

  /**
   * Checks if all message data will be saved together with each audit trail
   * entry.
   *
   * @return boolean
   */
  public boolean hasToSaveMessageData() {
    return saveMessageData;
  }

  public void setHasToSaveMessageData(boolean value) {
    saveMessageData = value;
  }

  public void removeCase(int index) {
    try {
      ProcessCase item = (ProcessCase) orderedCaseList.get(index);
      caseList.remove(item.getDescription());
      orderedCaseList.remove(index);
    } catch (IndexOutOfBoundsException e) {

    }
  }

  public void removeCase(ProcessCase item) {
    caseList.remove(item.getDescription());
    orderedCaseList.remove(item);
  }

  public ProcessCase addCase(String description) {
    ProcessCase curCase;
    if(!caseList.containsKey(description)) {
      curCase = new ProcessCase(description, this);
      caseList.put(description, curCase);
      orderedCaseList.add(curCase);
    } else {
      curCase = (ProcessCase)caseList.get(description);
    }
    return curCase;
  }

  public void removeCase(String description) {
    try {
      ProcessCase item = (ProcessCase) caseList.get(description);
      if (item != null) {
        orderedCaseList.remove(item);
        caseList.remove(description);
      }
    } catch(Exception e) {

    }
  }

  /**
   * Writes the log to the PrintWriter.
   *
   * @param writer PrintWriter
   */
  public void save(PrintWriter writer) {
    ProcessCase iterCase;
    Enumeration enumCases;
    int i;
    writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
    writer.print("<WorkflowLog xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
    writer.print("xsi:noNamespaceScemaLocation=\"http://tmitwww.tm.tue.nl/research/processmining/WorkflowLog.xsd\" ");
    writer.println("description=\" Conversion of e-mail log to process log\">");
    writer.println("<Source program=\"E-Mail log conversion program\"/>");
    writer.println("<Process id=\"e-mails\">");
    i = 0;
    enumCases = caseList.elements();
    while(enumCases.hasMoreElements()) {
      iterCase = (ProcessCase)enumCases.nextElement();
      iterCase.save(writer, i);
      i++;
    }
    writer.println("</Process>");
    writer.print("</WorkflowLog>");
  }

  /**
   * A flag, which specifies if the process case description must be equal to or
   * only a part of the subject line.
   *
   * @return boolean
   */
  public boolean isPartialCoincidenceCasesAllowed() {
    return allowPartialCoincidenceCases;
  }

  /**
   * A flag, which specifies if the process task description must be equal to or
   * only a part of the subject line.
   *
   * @return boolean
   */
  public boolean isPartialCoincidenceTasksAllowed() {
    return allowPartialCoincidenceTasks;
  }

  public void setPartialCoincidenceCasesAllowed(boolean value) {
    allowPartialCoincidenceCases = value;
  }

  public void setPartialCoincidenceTasksAllowed(boolean value) {
    allowPartialCoincidenceTasks = value;
  }

  /**
   * Mines the process log based on the e-mail log and user's preferences.
   */
  public void build() {
    Enumeration enumCases;
    ProcessCase curCase;
    if(orderedCaseList.isEmpty())
      extractAllPossibleCases();
    if(taskTypesList.isEmpty())
      extractPossibleTaskTypes();
    clear();
    distributeMessages();
    enumCases = caseList.elements();
    while(enumCases.hasMoreElements()) {
      curCase = (ProcessCase)enumCases.nextElement();
      curCase.build();
    }
  }

  private ProcessCase getCaseFromSubject(EMailMessage message) {
    ProcessCase curCase, tmp;
    int j, k;
    String curAlias;

    curCase = null;

    for(j=0;j<orderedCaseList.size();j++) {
        tmp = (ProcessCase)orderedCaseList.get(j);
        for(k=0;k<tmp.getAliases().size();k++) {
          curAlias = tmp.getAlias(k);
          if(allowPartialCoincidenceCases) {
            if(message.getSubject().toLowerCase().indexOf(curAlias.toLowerCase())!=-1) {
              curCase = tmp;
              break;
            }
          } else {
            if(message.getSubject().toLowerCase().equals(curAlias.toLowerCase())) {
              curCase = tmp;
              break;
            }
          }
        }
      }
      if(caseList.containsKey(message.getSubject())) {
        curCase = (ProcessCase)caseList.get(message.getSubject());
      }
    return curCase;
  }

  private ProcessCase getCaseFromSender(EMailMessage message) {
    ProcessCase curCase;
    EMailRecipient curRecipient;

    curCase = null;
    curRecipient = message.getFrom();
    if(caseList.containsKey(curRecipient.getName())) {
      curCase = (ProcessCase)caseList.get(curRecipient.getName());
    }
    return curCase;
  }

  private ProcessCase getCaseFromRecipient(EMailMessage message) {
    ProcessCase curCase;
    EMailRecipient curRecipient;
    int j;

    curCase = null;
    for(j=0;j<message.getBcc().size();j++) {
      curRecipient = (EMailRecipient)message.getBcc().get(j);
      if(caseList.containsKey(curRecipient.getName())) {
        curCase = (ProcessCase)caseList.get(curRecipient.getName());
        break;
      }
    }
    for(j=0;j<message.getCc().size();j++) {
      curRecipient = (EMailRecipient)message.getCc().get(j);
      if(caseList.containsKey(curRecipient.getName())) {
        curCase = (ProcessCase)caseList.get(curRecipient.getName());
        break;
      }
    }
    for(j=0;j<message.getTo().size();j++) {
      curRecipient = (EMailRecipient)message.getTo().get(j);
      if(caseList.containsKey(curRecipient.getName())) {
        curCase = (ProcessCase)caseList.get(curRecipient.getName());
        break;
      }
    }
    return curCase;
  }

  private void distributeMessages() {
    int i, j;

    ProcessCase curCase, tmp;
    EMailMessage iterMessage;
    EMailRecipient curRecipient;

    if(orderedCaseList.isEmpty())
      return;

    for(i=0;i<eMailLog.getOrderedMessages().size();i++) {
      iterMessage = (EMailMessage)eMailLog.getOrderedMessages().get(i);
      if(!iterMessage.isActive())
        continue;
      curCase = null;
      switch(caseSource) {
        case CASE_SENDER:
          curCase = getCaseFromSender(iterMessage);
          break;
        case CASE_RECIPIENT:
          curCase = getCaseFromRecipient(iterMessage);
          break;
        case CASE_SUBJECT:
          curCase = getCaseFromSubject(iterMessage);
          break;
        case CASE_ANYWHERE:
          curCase = getCaseFromSender(iterMessage);
          if(curCase==null) {
            curCase = getCaseFromRecipient(iterMessage);
          }
          if(curCase==null) {
            curCase = getCaseFromSubject(iterMessage);
          }
          break;
        case CASE_THREAD:
          if(iterMessage.getSubject().toLowerCase().startsWith("re:")) {
            for(j=0;j<orderedCaseList.size();j++) {
              tmp = (ProcessCase)orderedCaseList.get(j);
              if(iterMessage.getSubject().toLowerCase().indexOf(tmp.getDescription().toLowerCase())!=-1) {
                curCase = tmp;
                break;
              }
            }
          } else {
            curCase = (ProcessCase)caseList.get(iterMessage.getSubject());
          }
          break;
        case CASE_SINGLE_CASE:
          curCase = (ProcessCase)orderedCaseList.get(0);
          break;
        default:
          curCase = (ProcessCase)caseList.get(iterMessage.getCase());
          break;
      }
      if(curCase!=null) {
        curCase.addMessage(iterMessage);
      }
    }
  }

  /**
   * Clears all mined process log information.
   */
  public void clear() {
    ProcessCase tmp;
    for(int i=0;i<orderedCaseList.size();i++) {
      tmp = (ProcessCase)orderedCaseList.get(i);
      tmp.clear();
    }
  }

  /**
   * Extracts possible names of process instances (cases) according to the
   * user's preferences.
   */
  public void extractAllPossibleCases() {
    Enumeration enumMessages;
    EMailMessage iterMessage;
    EMailRecipient tmp;
    ProcessCase curCase;
    String caseName;
    int i, j;

    caseList.clear();
    orderedCaseList.clear();
    switch (caseSource) {
      case CASE_SENDER:
      case CASE_RECIPIENT:
      case CASE_ANYWHERE:
        for(i=0;i<eMailLog.getEmployeesList().size();i++) {
          tmp = (EMailRecipient)eMailLog.getEmployeesList().get(i);
          if(!tmp.isActive())
            continue;
          caseName = tmp.getName();
          curCase = new ProcessCase(caseName, this);
          for(j=0;j<tmp.getAliases().size();j++) {
            curCase.addAlias(tmp.getAlias(j));
          }
          caseList.put(caseName, curCase);
          orderedCaseList.add(curCase);
        }
        break;
      default:
        enumMessages = eMailLog.getMessageList().elements();
        while (enumMessages.hasMoreElements()) {
          iterMessage = (EMailMessage) enumMessages.nextElement();
          caseName = "";
          if (iterMessage.isActive()) {
            switch (caseSource) {
              case CASE_LINKED_CONTACT:
                caseName = iterMessage.getCase();
                break;
              case CASE_SUBJECT:
                caseName = iterMessage.getSubject();
                break;
              case CASE_SINGLE_CASE:
                caseName = "Process instance";
                break;
              case CASE_THREAD:
                caseName = iterMessage.getSubject();
                if (caseName.startsWith("Re:")||caseName.startsWith("RE:"))
                  continue;
                break;
            }
            if(!caseName.equals("")) {
              if(!caseList.containsKey(caseName)) {
                curCase = new ProcessCase(caseName, this);
                caseList.put(caseName, curCase);
                orderedCaseList.add(curCase);
              }
            }
          }
        }
        break;
    }
  }

  /**
   * Extracts possible names of process instances (cases) according to the
   * user's preferences.
   */
  public void extractPossibleTaskTypes() {
    Enumeration enumMessages;
    EMailMessage iterMessage;
    String taskType;

    taskTypesList.clear();
    enumMessages = eMailLog.getMessageList().elements();
    while (enumMessages.hasMoreElements()) {
      iterMessage = (EMailMessage) enumMessages.nextElement();
      switch(taskSource) {
        case TASK_SUBJECT:
          taskType = iterMessage.getSubject();
          break;
        default:
          taskType = iterMessage.getTask();
          break;
      }
      addTaskType(taskType);
    }
  }

  public void saveSetup(PrintWriter writer) {
    writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
    writer.println("<ProcessLogOptions>");
    saveCases(writer);
    saveTasks(writer);
    saveEvents(writer);
    writer.println("</ProcessLogOptions>");
  }

  protected void saveCases(PrintWriter writer) {
    int i, j;
    ProcessCase curCase;

    writer.print("<Cases source=\""+Integer.toString(this.caseSource)+"\" ");
    writer.print(" partial=\""+Boolean.toString(this.allowPartialCoincidenceCases)+"\" ");
    writer.println(">");
    for(i=0;i<orderedCaseList.size();i++) {
      curCase = (ProcessCase)orderedCaseList.get(i);
      writer.println("\t<Case name=\""+curCase.getDescription()+"\">");
      for(j=0;j<curCase.getAliases().size();j++) {
        writer.println("\t\t<Alias>"+curCase.getAlias(j)+"</Alias>");
      }
      writer.println("\t</Case>");
    }
    writer.println("</Cases>");
  }

  protected void saveTasks(PrintWriter writer) {
    int i, j;
    ProcessTaskType curTaskType;
    writer.print("<Tasks source=\""+Integer.toString(this.taskSource)+"\" ");
    writer.println("partial=\""+Boolean.toString(this.allowPartialCoincidenceTasks)+"\">");
    for(i=0;i<this.taskTypesList.size();i++) {
      curTaskType=(ProcessTaskType)taskTypesList.get(i);
      writer.println("\t<Task name=\""+curTaskType.getName()+"\">");
      for(j=0;j<curTaskType.getAliases().size();j++) {
        writer.println("\t\t<Alias>"+curTaskType.getAlias(j)+"</Alias>");
      }
      writer.println("\t</Task>");
    }
    writer.println("</Tasks>");
  }

  protected void saveEvents(PrintWriter writer) {
    int i, j;
    ProcessEventType curEventType;

    writer.print("<Events source=\""+Integer.toString(this.eventSource)+"\" ");
    writer.print(" missing=\""+Boolean.toString(this.assignMissingEvents)+"\" ");
    writer.print(" replies=\""+Boolean.toString(this.ignoreReplies)+"\" ");
    writer.println(" saveMessageData=\""+Boolean.toString(this.saveMessageData)+"\">");
    for(i=0;i<this.eventTypes.size();i++) {
      curEventType = (ProcessEventType)eventTypes.get(i);
      writer.print("\t<Event name=\""+curEventType.getStandardName()+"\" ");
      writer.println("alias=\""+curEventType.getAliasName()+"\"/>");
    }
    writer.println("</Events>");
  }

  public void loadSetup(String fileName) {
    try {
    	File settingsFile = new File(fileName);
        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
        parserFactory.setValidating(false);
        parserFactory.setNamespaceAware(true);
        ProcessLogPreferencesSaxParser logParser = new ProcessLogPreferencesSaxParser(this);
        SAXParser parser = parserFactory.newSAXParser();
        parser.parse(settingsFile, logParser);
    } catch (IOException exception) {
        exception.printStackTrace();
    } catch (SAXException exception) {
        exception.printStackTrace();
    } catch (ParserConfigurationException exception) {
        exception.printStackTrace();
    } catch (FactoryConfigurationError exception) {
        exception.printStackTrace();
    }

  }

}
